
Push notification fallbacks give a message somewhere else to go when push can't deliver it. In Courier, that's one routing object on the send: try push first, then email, then SMS, and stop at the first channel that works. A user without a push token still gets the email, and you don't write any retry or channel-switching code.
Courier handles fallback at two levels. Channel fallback moves the message to a different channel, like push to email. Provider failover switches to a backup service inside the same channel, like Amazon SES to SendGrid. You can use either one, or both on the same send.
Push depends on a valid device token and a working push provider. Courier handles each failure like this:
single route moves to the next channel.Not every notification needs a backup channel. Put fallbacks on the ones where a missed message costs the user something:
Every Courier message routes in two steps. It picks channels from routing.channels, then picks providers inside each channel. Each step has a method:
| Decision | Set with | single | all |
|---|---|---|---|
| Which channels | routing.method | Tries channels in order, stops at the first that delivers | Sends on every channel in the list |
| Which providers | The channel's routing_method | Tries providers in order, stops at the first that delivers (the default) | Sends through every provider on the channel |
Under single, everything after the first option is its backup.
This send tries push, then email, then SMS. A user with no push token gets the email, and SMS only goes out if email fails too:
import Courier from "@trycourier/courier";
const client = new Courier({ apiKey: process.env.COURIER_API_KEY });
const { requestId } = await client.send.message({
message: {
to: { user_id: "user_123" },
template: "YOUR_TEMPLATE_ID",
routing: {
method: "single",
channels: ["push", "email", "sms"],
},
},
});Route to channel names like push and email, not provider keys. Courier recommends this because it keeps provider failover and channel priority working inside each channel. You can name a single provider instead, but then Courier sends through that one provider only. See Send to a provider for when that's worth it.
FCM and APNs are two providers inside the one push channel, and a channel's routing_method defaults to single. So a user with an iPhone and an Android tablet gets the push on whichever device Courier reaches first. To push to both, set the push channel's routing_method to all. It goes in message.channels, not in routing:
const { requestId } = await client.send.message({
message: {
to: { user_id: "user_123" },
template: "YOUR_TEMPLATE_ID",
routing: {
method: "single",
channels: ["push", "email"],
},
channels: {
push: { routing_method: "all" },
},
},
});Install both APNs and Firebase FCM under Integrations so Courier can use them. A user with only an iPhone gets the same result either way, because Courier skips FCM when there's no FCM token. If push fails on every provider, the route still falls back to email.
For internal alerts, workplace chat is often a better backup than email. This send tries push, then Slack, then email:
const { requestId } = await client.send.message({
message: {
to: { user_id: "user_123" },
template: "YOUR_TEMPLATE_ID",
routing: {
method: "single",
channels: ["push", "slack", "email"],
},
},
});Here slack is the Slack provider key, so that step goes through Slack specifically. Courier reads the Slack target (a channel, user ID, or email, plus your bot token) from the user's profile. The Slack integration docs cover the profile shape, and Microsoft Teams works the same way with its own provider.
An entry in channels can be a nested routing object with its own method. Here the Inbox message always goes out, and the user also gets push, or email if push can't deliver:
{
"method": "all",
"channels": [
"inbox",
{ "method": "single", "channels": ["push", "email"] }
]
}You don't have to put routing on every send. A routing strategy saves the channel order, fallback, and providers once, and every template that uses it picks up your edits on its next send. Build one in Design Studio or with the API. An inline routing object on a send replaces the strategy's channel list and method for that one message, while the strategy's provider order and channel conditions still apply.
Channel fallback covers the user's side. Provider failover covers yours: if your email provider can't take the message, Courier switches to a backup provider on the same channel automatically.
List the providers for a channel in priority order. This send uses Amazon SES first and SendGrid as the backup:
const { requestId } = await client.send.message({
"Idempotency-Key": "order-1234-shipped",
message: {
to: { user_id: "user_123" },
template: "YOUR_TEMPLATE_ID",
routing: {
method: "single",
channels: ["email"],
},
channels: {
email: {
providers: ["ses", "sendgrid"],
routing_method: "single",
},
},
},
});Courier moves to the next provider when one can't take the message or returns an error. It retries transient errors, like a rate limit or a dropped connection, before moving on. Every attempt shows in the message's log, so you can see which provider delivered.
Retrying with the same Idempotency-Key returns the original response instead of sending again. Use one key per logical event, like one order shipping.
Failover on errors works on every plan. On an Enterprise plan, Courier also moves past a provider or channel that's slow rather than failed. It gives each provider 5 minutes and each channel 30 minutes by default, and you can change both per send. On other plans, a send that sets provider or channel timeouts returns 402. The timeouts reference has the details.
A backup channel never gets around a user's opt-out. Courier resolves preferences on top of routing, at send time:
UNSUBSCRIBED. Courier doesn't reroute around it.custom_routing) and the topic has channel selection turned on, their channels replace the template's routing.REQUIRED always delivers, and users can't opt out of it. Use it for security alerts and other messages that have to reach someone.Send to a stable user_id and map the template to the right topic, and Courier applies all of this for you.
Fallback fires when a channel fails, not when a user ignores a message. If APNs accepts a push and the user never opens it, the send succeeded and the route is done.
Two statuses matter here. SENT means Courier handed the message to the provider. DELIVERED means the provider confirmed it landed, which only happens once delivery tracking is set up for that provider. Neither one tells you someone read it.
For "remind them by email if they haven't finished setup," use Courier Journeys. Add a delay node, fetch the user's current state, and branch: send the email only if setup is still incomplete.
Use test users and check the log for each case:
| Test | What you should see |
|---|---|
| User has a push token | Push sends, and no email goes out under single |
| User has no push token, has an email | Push channel fails, and the email sends |
| Primary email provider fails | Backup provider takes the message |
| User opted out of the topic | Send blocked with UNSUBSCRIBED, no fallback |
| User set custom routing for the topic | Their channels are used, not the template's |
Comparing platforms? Automatic provider failover is built into Courier. See Courier vs Novu and Courier vs Customer.io.
If every option in the route fails, the message fails. Every attempt, on every channel and provider, shows in the message's log, so you can see exactly where it stopped.
No. If the push provider accepts the message, the send succeeded. To follow up with email when a user doesn't act, build that in Journeys with a delay and a branch on the user's current state.
No. An opt-out blocks the send for that topic, and Courier doesn't reroute around it. If a message has to reach everyone, map it to a topic with a REQUIRED default.
Set the push channel's routing_method to all in message.channels, and install both APNs and Firebase FCM. Courier then sends through every push provider the user has a token for.
Yes. Courier fails over between providers and channels on errors on every plan. Moving past a provider or channel that's slow, rather than failed, requires an Enterprise plan.
Channels. Routing to push or email keeps provider failover working inside each channel. Name a provider key like slack only when you want that one provider specifically.
Pick your most important notification, add routing: { method: "single", channels: ["push", "email"] }, and send it to a test user without a push token. The routing guide covers every pattern in this post. Sign up free to try it.

What Siri AI means for your email, SMS, and push
Siri AI shipped with iOS 27 and changes what happens to a message after it's delivered. It reads urgent notifications aloud, searches Apple Mail and Messages, can delete or unsubscribe from your email on request, and will soon answer "where's my order?" through apps. Here's what each change means for your email, SMS, and push, and what to do now.

A marketer's guide to iOS 27 and priority notifications
On iOS 27, Apple Intelligence ranks and silences push notifications, rewrites them into summaries, and reads urgent ones aloud. Mail sorts your email into categories, Messages screens texts from unknown numbers, and Gmail, Slack, and Teams add their own filters on top. Here's what each layer does, what you can control, and what to change this quarter.

How to protect your email domain and sending reputation
AI agents have made spoofing cheap, and a DMARC record at p=none won't stop it. Here's how to enforce DMARC without bouncing your own mail, keep password resets apart from newsletters, protect your sending reputation, read Google Postmaster Tools, and audit your domain with six AI prompts.